﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Design;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Marmat.Forms.Skin
{
    /// <summary>
    /// 提供一个列表控件。
    /// </summary>
    public partial class ListBox : Marmat.Forms.Skin.ScrollControl
    {
        /// <summary>
        /// 初始化 Marmat.Forms.Skin.ListBox 类的新实例。
        /// </summary>
        public ListBox()
        {
            SetStyle(ControlStyles.Selectable, true);
            SetStyle(ControlStyles.ResizeRedraw, true); //尺寸变化后绘制
            SetStyle(ControlStyles.Opaque, false); //改善绘图质量
            SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
            SetStyle(ControlStyles.AllPaintingInWmPaint, true);
            SetStyle(ControlStyles.UserPaint, true);
            SetStyle(ControlStyles.DoubleBuffer, true);
            this.Items = new ObjectCollection(this);
        }

        private readonly Dictionary<int, Rectangle> item_rect = new Dictionary<int, Rectangle>();
        private int selectedIndex = -1;
        private int mousemoveIndex = -1;

        /// <summary>
        /// 获取或设置 Marmat.Forms.Skin.ListBox 中项的高度。
        /// </summary>
        /// <returns>控件中项的高度（以像素为单位）。</returns>
        [DefaultValue(13)]
        public int ItemHeight { get; set; } = 13;

        /// <summary>
        /// 获取或设置要为此 Marmat.Forms.Skin.ListControl 显示的属性。
        /// </summary>        
        /// <returns>一个 System.String，指定 System.Windows.Forms.ListControl.DataSource 属性指定的集合中包含的对象属性的名称。默认值为空字符串("")。</returns>
        [DefaultValue(null)]
        public string DisplayMember { get; set; }

        /// <summary>
        /// 获取 Marmat.Forms.Skin.ListBox 的项。
        /// </summary>
        [Localizable(true), DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public virtual ObjectCollection Items { get; }

        /// <summary>
        /// 在所有者绘制的 System.Windows.Forms.ListBox 在视觉外观更改时发生。
        /// </summary>
        public event DrawItemEventHandler DrawItem;

        /// <summary>
        /// 创建所有者描述的 System.Windows.Forms.ListBox 和确定列表项的大小时发生。
        /// </summary>
        public event MeasureItemEventHandler MeasureItem;

        /// <summary>
        /// 选中SelectedIndex 属性更改后发生。
        /// </summary>
        public event EventHandler SelectedIndexChanged;


        /// <summary>
        /// 获取或设置当前选中项。
        /// </summary>
        [Browsable(false)]
        [DefaultValue(null)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public object SelectedItem
        {
            get
            {
                if (this.Items.Count > 0 && this.selectedIndex >= 0)
                    return this.Items[this.selectedIndex];
                else
                    return null;
            }
            set
            {
                if (value is null)
                    this.SelectedIndex = -1;
                else
                {
                    var index = this.Items.FindIndex(x => x == value);
                    if (index >= 0)
                        this.SelectedIndex = index;
                    else
                        throw new ArgumentException("当前赋予的值不在集合中。");
                }
            }
        }

        /// <summary>
        /// 获取或设置 System.Windows.Forms.ListBox 中当前选定项的从零开始的索引。       
        /// </summary>
        /// <remarks>当前选定项的从零开始的索引。 如果未选定任何项，则返回值为负一 (-1)。</remarks>
        [DefaultValue(-1)]
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public int SelectedIndex
        {
            get => selectedIndex;
            set
            {
                if (selectedIndex == value)
                    return;
                selectedIndex = value;
                this.SelectedIndexChanged?.Invoke(this, EventArgs.Empty);
                this.Invalidate();
            }
        }

        /// <summary>
        /// 确保指定节点可见。
        /// </summary>
        /// <param name="index"></param>
        public void SetItemVisible(int index)
        {
            var rect = new Rectangle(0, index * this.ItemHeight, this.Width, this.ItemHeight);
            int top = this.VerticalScroll.Value;
            int bottom = top + this.Height;
            int scroll = this.VerticalScroll.Value;
            if (top > rect.Y)
                scroll = rect.Y;
            else if (bottom < rect.Bottom)
                scroll += rect.Bottom - bottom;
            this.scrollableControlExtension.set_vertical_scroll_value(scroll);
            this.Invalidate();
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            item_rect.Clear();
            base.OnPaint(e);
            int width = this.ClientSize.Width - this.Padding.Right - this.Padding.Left;
            int top = this.Padding.Top;
            int scroll = this.VerticalScroll.Value;

            // 如果设计模式下并且列表为空时，绘制控件名称。
            if (this.DesignMode && this.Items.Count == 0)
                TextRenderer.DrawText(e.Graphics, this.Name, this.Font, new Rectangle(0, 0, this.Width, this.Height), this.ForeColor, TextFormatFlags.Left | TextFormatFlags.Top);

            for (int i = 0; i < this.Items.Count; i++)
            {
                object item = this.Items[i];

                int itemHeight = this.ItemHeight;
                if (this.MeasureItem != null)
                {
                    MeasureItemEventArgs args = new MeasureItemEventArgs(e.Graphics, i, itemHeight);
                    this.MeasureItem.Invoke(this, args);
                    itemHeight = args.ItemHeight;
                }
                int bottom = top + itemHeight;
                if (bottom > scroll && top < scroll + this.Height)
                {
                    Rectangle item_rectangle = new(this.Padding.Left, top - scroll, width, itemHeight);
                    DrawItemState state = DrawItemState.Default;
                    Color foreColor = this.ForeColor;
                    Color backColor = this.BackColor;
                    if (this.SelectedIndex == i)
                    {
                        state |= DrawItemState.Selected;
                        foreColor = Color.White;
                    }
                    if (this.mousemoveIndex == i)
                    {
                        state |= DrawItemState.HotLight;
                    }
                    DrawItemEventArgs args = new(e.Graphics, this.Font, item_rectangle, i, state, foreColor, backColor);
                    drawItem(args);
                    item_rect[i] = item_rectangle;
                }
                top += itemHeight;
            }
            this.AutoScrollMinSize = new Size(0, top + this.Padding.Bottom);
        }

        void drawItem(DrawItemEventArgs args)
        {
            if (this.DrawItem is null)
            {
                var item = this.Items[args.Index];
                args.DrawBackground();
                string display = item?.ToString();
                if (item != null && !string.IsNullOrEmpty(this.DisplayMember))
                    display = item.GetType().GetProperty(this.DisplayMember).GetValue(item, null)?.ToString();
                TextRenderer.DrawText(args.Graphics, display, this.Font, args.Bounds, args.ForeColor, Color.Transparent, TextFormatFlags.VerticalCenter);
            }
            else
            {
                this.DrawItem.Invoke(this, args);
            }
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);
            var collection = this.item_rect.ToList();
            if (collection.Exists(x => x.Value.Contains(e.Location)))
            {
                var item = this.item_rect.First(x => x.Value.Contains(e.Location));
                if (item.Key != this.mousemoveIndex)
                {
                    this.mousemoveIndex = item.Key;
                    this.Invalidate();
                }
            }
            else if (this.mousemoveIndex != -1)
            {
                this.mousemoveIndex = -1;
                this.Invalidate();
            }
        }
        protected override void OnMouseLeave(EventArgs e)
        {
            base.OnMouseLeave(e);
            if (this.mousemoveIndex != -1)
            {
                this.mousemoveIndex = -1;
                this.Invalidate();
            }
        }

        protected override void OnMouseClick(MouseEventArgs e)
        {
            base.OnMouseClick(e);
            if (this.Items.Count > 0)
            {
                var collection = this.item_rect.ToList();
                if (collection.Exists(x => x.Value.Contains(e.Location)))
                {
                    this.SelectedIndex = collection.FirstOrDefault(x => x.Value.Contains(e.Location)).Key;
                    this.ItemClick?.Invoke(this, new ItemEventArgs(this.SelectedItem));
                }
            }
        }

        /// <summary>
        /// 如果按下的是向上向下键,并且列表集合不为空的情况下,拦截焦点转移事件。
        /// </summary>
        /// <param name="keyData"></param>
        /// <returns></returns>
        protected override bool ProcessDialogKey(Keys keyData)
        {
            int count = this.Items.Count;
            if (count > 0)
            {
                if (keyData == Keys.Down)
                {
                    if (count - 1 > this.SelectedIndex)
                    {
                        this.SelectedIndex++;
                        this.SetItemVisible(this.SelectedIndex);
                    }
                }
                else if (keyData == Keys.Up)
                {
                    if (this.SelectedIndex > 0)
                    {
                        this.SelectedIndex--;
                        this.SetItemVisible(this.SelectedIndex);
                    }
                }
                return true;
            }
            return base.ProcessDialogKey(keyData);
        }
    }
}
